import pathlib

from loguru import logger
from clang.cindex import Config, Cursor, TranslationUnit, Index, Diagnostic, CursorKind

from .c_typer import CTypeFinder
from .clang_decl_parser import CType, Decl, UNEXPOSED_DECL, ENUM_DECL, FUNCTION_DECL, UNION_DECL, STRUCT_DECL, FIELD_DECL, TYPEDEF_DECL


class PyCtypesMaker:
    clang_options = TranslationUnit.PARSE_SKIP_FUNCTION_BODIES \
                    | TranslationUnit.PARSE_PRECOMPILED_PREAMBLE \
                    | TranslationUnit.PARSE_INCLUDE_BRIEF_COMMENTS_IN_CODE_COMPLETION
    NEED_DECLs = [
        CursorKind.UNEXPOSED_DECL,
        CursorKind.LINKAGE_SPEC,
        CursorKind.STRUCT_DECL,
        CursorKind.FIELD_DECL,
        CursorKind.TYPEDEF_DECL,
        CursorKind.ENUM_DECL,
        CursorKind.FUNCTION_DECL,
        CursorKind.UNION_DECL
    ]

    DECLParser = {item.__name__: item for item in [
        UNEXPOSED_DECL,
        STRUCT_DECL,
        FIELD_DECL,
        TYPEDEF_DECL,
        ENUM_DECL,
        FUNCTION_DECL,
        UNION_DECL
    ]}

    def __init__(self, target: pathlib.Path, args: list[str], libclang_file_path: pathlib.Path, outfile: pathlib.Path = "out.py"):
        Config.set_library_file(libclang_file_path)
        self.index = Index.create()
        self.root_tu = self.index.parse(target, args=args, options=self.clang_options)
        warnings = [w for w in self.root_tu.diagnostics if w.severity == Diagnostic.Warning]
        errors = [w for w in self.root_tu.diagnostics if w.severity == Diagnostic.Error]
        fatals = [w for w in self.root_tu.diagnostics if w.severity == Diagnostic.Fatal]
        if warnings:
            [logger.warning(warning) for warning in warnings]
        if errors:
            [logger.error(err) for err in errors]
        if fatals:
            [logger.error(fatal) for fatal in fatals]
        logger.info(args)
        if len(errors) + len(fatals) == 0:
            logger.info(f"{target} parses success.")
        else:
            raise ValueError("解析失败")

        self.target = target
        self.outfile = outfile
        self.target_cursor_set = []
        self.types: dict[int, CType] = dict()
        self.decls: dict[int, Decl] = dict()
        self.headers: set[str] = set()

        self.ctype_finder = CTypeFinder()
        self.walk_translate_unit()

    def walk_translate_unit(self):
        for item in self.root_tu.cursor.get_children():
            if item.location.file.name == self.target.as_posix():
                self.target_cursor_set.append(item)
            else:
                self.headers.add(item.location.file.name)

        for item in self.target_cursor_set:
            if item.kind in self.NEED_DECLs:
                self.dispatch_decl(item)

        self.to_ctypes_file_v1()

    def _parse_decl(self, cursor: Cursor, parser) -> Decl:
        ret = self.ctype_finder.find_declaration(cursor.type)
        if not isinstance(ret, list):
            ret = [ret]
        ret = list(filter(lambda o: o is not None, ret))
        depends = []
        for item in ret:
            if item.hash not in self.types:
                obj = CType(declaration=item, definition=item.get_definition())
                self.types[item.hash] = obj

            if cursor.hash != item.hash:
                depends.append(self.types[item.hash])

        if cursor.hash not in self.decls:
            decl = parser(declaration=cursor, depend_types=depends)
            decl.parse(self)
            self.decls[cursor.hash] = decl
        else:
            decl = self.decls[cursor.hash]
        return decl


    def dispatch_decl(self, cursor: Cursor) -> Decl | None:
        parser = self.DECLParser.get(cursor.kind.name)
        if parser:
            obj = self._parse_decl(cursor, parser)
            return obj
        else:
            logger.warning(f"ignore {cursor.kind.name} {cursor.location.file.name}")

    def to_ctypes_file_v1(self):
        with open(str(self.outfile), "w", encoding="utf-8") as f:
            f.write("import ctypes as ct\n\n# includes:\n")
            f.write("\n".join(['# ' + h for h in self.headers]))
            f.write(f"\n# target: {str(self.target)}\n\n")
            for decl in self.decls.values():
                if isinstance(decl, (STRUCT_DECL, )):
                    f.write(decl.print_declaration())
            for decl in self.decls.values():
                if isinstance(decl, (STRUCT_DECL, TYPEDEF_DECL)):
                    f.write(decl.print_definition())

            for decl in self.decls.values():
                if isinstance(decl, (FUNCTION_DECL, )):
                    if decl.is_external:
                        f.write(str(decl))
